package com.sinosoft.gateway.filter;

import com.netflix.zuul.context.RequestContext;
import com.sinosoft.gateway.config.Constants;
import com.sinosoft.gateway.model.RequestMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.annotation.Contract;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.hibernate.validator.internal.util.Contracts;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter;
import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

@Component
@Slf4j
public class SimpleHostFilter extends SimpleHostRoutingFilter {

    private static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("/{2,}");
    private final Timer connectionManagerTimer = new Timer("SimpleHostRoutingFilter.connectionManagerTimer", true);
    private ProxyRequestHelper helper;
    private ZuulProperties.Host hostProperties;
    private ApacheHttpClientConnectionManagerFactory connectionManagerFactory;
    private ApacheHttpClientFactory httpClientFactory;
    private HttpClientConnectionManager connectionManager;
    private boolean sslHostnameValidationEnabled;
    private boolean forceOriginalQueryStringEncoding;
    private CloseableHttpClient httpClient;
    private boolean customHttpClient = false;

    public SimpleHostFilter(ProxyRequestHelper helper, ZuulProperties properties, ApacheHttpClientConnectionManagerFactory connectionManagerFactory, ApacheHttpClientFactory httpClientFactory) {
        super(helper, properties, connectionManagerFactory, httpClientFactory);
        this.helper = helper;
        this.hostProperties = properties.getHost();
        this.sslHostnameValidationEnabled = properties.isSslHostnameValidationEnabled();
        this.forceOriginalQueryStringEncoding = properties.isForceOriginalQueryStringEncoding();
        this.connectionManagerFactory = connectionManagerFactory;
        this.httpClientFactory = httpClientFactory;
    }

    @PostConstruct
    private void initialize() {
        if (!this.customHttpClient) {
            this.connectionManager = this.connectionManagerFactory.newConnectionManager(!this.sslHostnameValidationEnabled, this.hostProperties.getMaxTotalConnections(), this.hostProperties.getMaxPerRouteConnections(), this.hostProperties.getTimeToLive(), this.hostProperties.getTimeUnit(), (RegistryBuilder)null);
            this.httpClient = this.newClient();
            this.connectionManagerTimer.schedule(new TimerTask() {
                public void run() {
                    if (connectionManager != null) {
                        connectionManager.closeExpiredConnections();
                    }
                }
            }, 30000L, 5000L);
        }
    }

    @PreDestroy
    public void stop() {
        this.connectionManagerTimer.cancel();
    }

    public Object run() {
//        super.run();
        log.info("SimpleHostFilter。。。。。。。。");
        RequestContext context = RequestContext.getCurrentContext();
        if(context.get(Constants.IS_DEBUG_REQUEST_FLAG) != null){
            try {
                RequestMessage requestMessage = (RequestMessage) context.get("exec.requestBody");
                HttpServletRequest request = context.getRequest();
                MultiValueMap<String, String> headers = buildHeader(requestMessage.getHeader());
                MultiValueMap<String, String> params = this.helper.buildZuulRequestQueryParams(request);
                String verb = requestMessage.getHttpMethod().toUpperCase();
                InputStream requestEntity = IOUtils.toInputStream(requestMessage.getRequestBody(),"utf-8");
                if (this.getContentLength(request) < 0L) {
                    context.setChunkedRequestBody();
                }
                String uri = UriUtils.encodePath(requestMessage.getUrl(), "utf-8");
                this.helper.addIgnoredHeaders(new String[0]);
                CloseableHttpResponse response = this.forward(this.httpClient, verb, uri, request, headers, params, requestEntity);
                this.setResponse(response);
                return null;
            } catch (Exception var9) {
                throw new ZuulRuntimeException(this.handleException(var9));
            }
        }else{
            return super.run();
        }
    }


    private MultiValueMap<String, String> buildHeader(Map<String, String> headerMap){
        MultiValueMap<String, String> headers = new HttpHeaders();
        for(Map.Entry<String, String> entry : headerMap.entrySet()){
            headers.set(entry.getKey(), entry.getValue());
        }
        return headers;
    }


    private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb, String uri, HttpServletRequest request, MultiValueMap<String, String> headers, MultiValueMap<String, String> params, InputStream requestEntity) throws Exception {
        Map<String, Object> info = this.helper.debug(verb, uri, headers, params, requestEntity);
        URL host = RequestContext.getCurrentContext().getRouteHost();
        HttpHost httpHost = this.getHttpHost(host);
        uri = StringUtils.cleanPath(MULTIPLE_SLASH_PATTERN.matcher(host.getPath() + uri).replaceAll("/"));
        long contentLength = this.getContentLength(request);
        ContentType contentType = null;
        if (request.getContentType() != null) {
            contentType = ContentType.parse(request.getContentType());
        }

        InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength, contentType);
        HttpRequest httpRequest = this.buildHttpRequest(verb, uri, entity, headers, params, request);
        log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " " + httpHost.getSchemeName());
        CloseableHttpResponse zuulResponse = this.forwardRequest(httpclient, httpHost, httpRequest);
        this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(), this.revertHeaders(zuulResponse.getAllHeaders()));
        return zuulResponse;
    }

    private HttpHost getHttpHost(URL host) {
        HttpHost httpHost = new HttpHost(host.getHost(), host.getPort(), host.getProtocol());
        return httpHost;
    }

    private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient, HttpHost httpHost, HttpRequest httpRequest) throws IOException {
        return httpclient.execute(httpHost, httpRequest);
    }

    private MultiValueMap<String, String> revertHeaders(Header[] headers) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap();
        Header[] var3 = headers;
        int var4 = headers.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            Header header = var3[var5];
            String name = header.getName();
            if (!map.containsKey(name)) {
                map.put(name, new ArrayList());
            }

            ((List)map.get(name)).add(header.getValue());
        }

        return map;
    }

    private void setResponse(HttpResponse response) throws IOException {
        RequestContext.getCurrentContext().set("zuulResponse", response);
        this.helper.setResponse(response.getStatusLine().getStatusCode(), response.getEntity() == null ? null : response.getEntity().getContent(), this.revertHeaders(response.getAllHeaders()));
    }
}
